package Network

import (
	"SQL/FSDB"
	"encoding/base64"
	"encoding/json"
	"io"
	"logs"
	"mime/multipart"
	"net/http"
	"os"
	"strings"
	"time"
	"token"
)

// FileInfo fileURL是把文件信息结构体FileInfo base64编码后的字符串
// fileURL可解码成文件信息结构体FileInfo
type FileInfo struct {
	SenderID   string `json:"SenderID"`
	ReceiverID string `json:"ReceiverID"`
	FileName   string `json:"FileName"`
	FileType   string `json:"FileType"`   //image file
	CreateTime string `json:"CreateTime"` //时间参数精确度极高的情况下，base64编码后的文件名不会重名
}

// 文件为大小文件的依据是ParseMultipartForm的maxMemory参数的大小
// 获取文件大小的接口，上传小文件时，文件类型为：multipart.sectionReadCloser
type Size interface {
	Size() int64
}

// 获取文件信息的接口，上传大文件时，文件类型为： *os.File
type Stat interface {
	Stat() (os.FileInfo, error)
}

const (
	maxFileSizeOfChatImage  = 20 << 20 //聊天图片最大20M
	maxFileSizeOfUploadFile = 1 << 30  //聊天文件最大1G
	maxMemory               = 32 << 20 //临时空间32M
)

func uploadFile(w http.ResponseWriter, req *http.Request) (errCode int, errMsg string, result interface{}) {
	errCode = Success
	err := req.ParseMultipartForm(maxMemory)
	if err != nil {
		logs.Print("req.ParseMultipartForm执行错误：", err.Error())
		errCode = errParseFileFailed
		errMsg = "MultipartForm解析失败，错误详情：" + err.Error()
		return
	}

	// ---------------------------------------------- 参数检查
	var isValidForm = false
	for _, header := range req.Header["Content-Type"] {
		if strings.HasPrefix(header, "multipart/form-data") {
			isValidForm = true
		}
	}
	if !isValidForm {
		logs.Print("Header 参数设置有误")
		errCode = errInvalidEmptyArgs
		errMsg = "Header 参数设置有误!"
		return
	}

	ok := false

	tokenStr := req.FormValue("token")
	if ok, errCode, errMsg = isValidTokenInHTTP(tokenStr); !ok {
		return
	}

	talkerIsUser := false
	talkerIsUserStr := req.FormValue("talkerIsUser")
	if ok, talkerIsUser, errCode, errMsg = isValidBoolInHTTP(talkerIsUserStr); !ok {
		errMsg = "talkerIsUser不是有效的布尔型参数！"
		return
	}

	userID := "" //"100000001"
	var user *token.User
	if user, ok = token.GetUser(tokenStr); !ok {
		errCode = errInvalidToken
		errMsg = mInvalidToken
		return
	}
	userID = user.ID
	talkerID := req.FormValue("talkerID")
	if ok, errCode, errMsg = isExistTalkerID(userID, talkerID, talkerIsUser); !ok {
		return
	}

	fileType := req.FormValue("fileType")
	switch fileType {
	case "image":
		fallthrough
	case "file":
	default:
		errCode = errInvalidFileType
		errMsg = mInvalidFileType + fileType + "不是有效的文件类型！"
		return
	}

	file, fileHeader, err := req.FormFile("file")
	if err != nil {
		errCode = errParseFileFailed
		errMsg = mParseFileFailed + err.Error()
		return
	}
	defer file.Close()

	var fileSize int64
	if iFileSize, ok := file.(Size); ok {
		fileSize = iFileSize.Size()
	} else if iFileStat, ok := file.(Stat); ok {
		fileInfo, _ := iFileStat.Stat()
		fileSize = fileInfo.Size()
	}
	fileName := fileHeader.Filename
	if fileSize == 0 {
		errCode = errTooLargeUploadFile
		errMsg = "文件大小不能为0！"
		return
	}
	if fileType == "image" && fileSize > maxFileSizeOfChatImage {
		errCode = errTooLargeChatImage
		errMsg = mTooLargeChatImage
		return
	}
	if fileSize > maxFileSizeOfUploadFile {
		errCode = errTooLargeUploadFile
		errMsg = mTooLargeUploadFile
		return
	}
	// ---------------------------------------------- 单聊 or 群聊
	var resultMap map[string]string
	if talkerIsUser {
		if ok, errCode, errMsg = isExistTalkerID(userID, talkerID, true); !ok {
			return
		}
		errCode, errMsg, resultMap = writeUploadFile(userID, talkerID, talkerIsUser, file, fileType, fileName, fileType)
		if errCode == Success {
			writeResponseToClient(talkerID, &ServerResponse{
				Method:  "/receiveMessage",
				Code:    Success,
				Message: "",
				Result: ServerMsg{
					Type:        MsgTypeFriend,
					ContentType: fileType,
					Content: FriendMsg{
						FriendID:   userID,
						FriendName: resultMap["SenderName"],
						FriendIcon: resultMap["SenderIcon"],
						MsgID:      resultMap["LastMsgID"],
						Msg:        resultMap["EncodedFileName"],
						CreateTime: resultMap["CreateTime"],
					},
				},
			})
		}
	} else {
		if ok, errCode, errMsg = isExistTalkerID(userID, talkerID, false); !ok {
			return
		}
		errCode, errMsg, resultMap = writeUploadFile(userID, talkerID, talkerIsUser, file, fileType, fileName, fileType)
		if errCode == Success {
			writeResponseToGroup(userID, talkerID, resultMap["createTime"], ServerResponse{
				Method:  "/receiveMessage",
				Code:    Success,
				Message: "新消息！",
				Result: ServerMsg{
					Type:        MsgTypeGroup,
					ContentType: fileType,
					Content: GroupMsg{
						GroupID:    talkerID,
						GroupName:  resultMap["GroupName"],
						GroupIcon:  resultMap["GroupIcon"],
						SenderName: resultMap["SenderName"],
						SenderIcon: resultMap["SenderIcon"],
						MsgID:      resultMap["LastMsgID"],
						Msg:        resultMap["EncodedFileName"],
						CreateTime: resultMap["CreateTime"],
					},
				},
			})
		}
	}
	result = map[string]string{"MsgID": resultMap["LastMsgID"], "Msg": resultMap["EncodedFileName"], "MsgCreateTime": resultMap["CreateTime"]}
	return
}

func writeUploadFile(senderID string, receiverID string, talkerIsUser bool, file multipart.File, filePath string, fileName string, fileType string) (errCode int, errMsg string, result map[string]string) {

	result = map[string]string{}
	// ---------------------------------------------- 生成文件信息
	fileInfo := FileInfo{SenderID: senderID, ReceiverID: receiverID, FileName: fileName, FileType: fileType, CreateTime: time.Now().String()}

	fileInfoData, err := json.Marshal(fileInfo)
	if err != nil {
		errCode = errMarshalJSONFailed
		errMsg = "json.Marshal出错，错误详情：" + err.Error()
		return
	}
	encodedFileName := base64.StdEncoding.EncodeToString(fileInfoData)
	result["EncodedFileName"] = encodedFileName
	// ---------------------------------------------- 检查文件是否已存在
	writeFileName := "./private/" + filePath + "/" + encodedFileName
	isExist, err := isPathOrFileExists(writeFileName)
	if err != nil {
		errCode = errWriteFileFailed
		errMsg = mWriteFileFailed + err.Error()
		return
	}
	if isExist {
		errCode = errWriteFileFailed
		errMsg = "文件已存在！"
		return
	}

	// ---------------------------------------------- 更新数据库
	msgInsertSQL := `INSERT INTO messages(sender,receiver,type,content)
					VALUES($1,$2,$3,$4)
					RETURNING id,createTime`
	tx, db, err := FSDB.BeginTx()
	if err != nil {
		errCode = errEndTx
		errMsg = "FSDB.BeginTx发生错误：" + err.Error()
		return
	}
	defer func() {
		if errCode == Success {
			tx.Commit()
		} else {
			tx.Rollback()
		}
		closeDB(db)
	}()

	miStmt, miRows, err := FSDB.QueryTx(tx, msgInsertSQL, senderID, receiverID, filePath, encodedFileName)
	if err != nil {
		errCode = errEndTx
		errMsg = "FSDB.QueryTx msgInsertSQL发生错误：" + err.Error()
		return
	}
	defer miStmt.Close()
	defer miRows.Close()

	lastMsgID := ""
	msgCreateTime := ""
	for miRows.Next() {
		err = miRows.Scan(&lastMsgID, &msgCreateTime)
		if err != nil {
			errCode = errEndTx
			errMsg = "miRows.Scan发生错误：" + err.Error()
			return
		}
	}
	miRows.Close()
	result["LastMsgID"] = lastMsgID
	result["CreateTime"] = msgCreateTime

	sessionInsertSQL := ``
	if talkerIsUser {
		sessionInsertSQL = `SELECT updateUserSession($1,$2,$3);`
	} else {
		sessionInsertSQL = `SELECT updateGroupSession($1,$2,$3);`
	}
	_, err = FSDB.ExecTx(tx, sessionInsertSQL, senderID, receiverID, lastMsgID)
	if err != nil {
		errCode = errEndTx
		errMsg = "FSDB.ExecTx sessionInsertSQL发生错误：" + err.Error()
		return
	}

	senderName := ""
	senderIcon := ""
	errCode, errMsg, senderName, senderIcon = objectInfoFromDBInContext(tx, senderID)
	if errCode != Success {
		return
	}
	result["SenderName"] = senderName
	result["SenderIcon"] = senderIcon

	groupName := ""
	groupIcon := ""
	if !talkerIsUser {
		errCode, errMsg, groupName, groupIcon = objectInfoFromDBInContext(tx, receiverID)
		if errCode != Success {
			return
		}
		result["GroupName"] = groupName
		result["GroupIcon"] = groupIcon
	}

	// ---------------------------------------------- 文件写入磁盘
	f, err := os.OpenFile(writeFileName, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
		errCode = errWriteFileFailed
		errMsg = mWriteFileFailed + err.Error()
		return
	}
	defer f.Close()

	_, err = io.Copy(f, file)
	if err != nil {
		errCode = errWriteFileFailed
		errMsg = mWriteFileFailed + err.Error()
		return
	}

	// 更新用户在线时间
	updateUserOnlineTime(tx, senderID, currentTime())

	return Success, "发送成功！", result
}

func uploadFileTest(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Content-Type", "text/html;charset=utf-8")
	w.Write([]byte(tpl))
}

const tpl = `<html>
<head>
<title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action="http://127.0.0.1:8001/uploadFile" method="post">
 <input type="file" name="file" /><br><br>
 token:<input type="text" name="token" value="4441b67acfcc4a21224d17e6868c4905"/><br>
 talkerID:<input type="text" name="talkerID" value="100000002"/><br>
 talkerIsUser:<input type="text" name="talkerIsUser" value="true"/><br>
 fileType:<input type="text" name="fileType" value="image"/><br>
 <input type="hidden" name="fileUploadForm" value="..."/><br><br>
 <input type="submit" value="upload" />
</form>
</body>
</html>`
